package ru.ifmo.ctd.intsys.afanasyeva.boosting;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AdaBoostedClassifier implements Classifier {
	private final Classifier[] classifiers;
	private List<Double> factors;
	private List<Classifier> chosen;
	
	public AdaBoostedClassifier(Classifier[] classifiers) {
		this.classifiers = classifiers;
		this.factors = new ArrayList<Double>(classifiers.length);
		chosen = new ArrayList<Classifier>(classifiers.length);
	}

	@Override
	public double getOutput(double[] input) {
		double sum = 0;
		double sa = 0;
		for (int i = 0, len = factors.size(); i < len; i++) {
			sa += factors.get(i);
			sum += factors.get(i) * chosen.get(i).getOutput(input);
		}
	return sum / sa;
	}

	@Override
	public void train(double[][] input, double[] output, int steps) {
		int n = output.length;
		double[][] w = new double[steps][n];
		Arrays.fill(w[0], (double)1 / n);
		
		for (int m = 1; m < steps; m++) {
			double[] minDescr = findMinimizer(w[m - 1], input, output, m);
			Classifier c = classifiers[(int) minDescr[0]];
			chosen.add(c);
			double eps = (double) (minDescr[1] + 1) / 2;
			double alphaM = 0.5 * Math.log((double)(1 - eps) / eps);
			factors.add(alphaM);	
			
			double cm = 0;
			for (int t = 0; t < n; t++) {
				cm += w[m][t] = w[m - 1][t] * Math.exp(-1 * output[t] * alphaM * c.getOutput(input[t]));
			}
			for (int t = 0; t < n; t++) {
				w[m][t] /= cm;
			}
			
			
		}
	}

	private double sum(double[] wm1, double[][] input, double[] output, 
			Classifier c, int m) {
		double sum = 0;
		//t < m
		for (int t = 0; t < output.length; t++) {
			sum += wm1[t]*output[t]*c.getOutput(input[t]);
		}
		return -1 * sum;
	}
	
	private double[] findMinimizer(double[] wm1, double[][] input, double[] output, int m) {
		int mini = 0;
		double min = sum(wm1, input, output, classifiers[mini], m);
		for (int i = 0; i < classifiers.length; i++) {
			double candidate = sum(wm1, input, output, classifiers[i], m);
			if (candidate < min) {
				min = candidate;
				mini = i;
			}
		}
		return new double[] {mini, min};
	}
}
